package com.aphrodite.util;

import com.aphrodite.security.CheckSum;

/**
 * TBD. Validates any input.
 * <p>
 * Performs an indepth validation of the given input.
 * </p>
 * 
 * @author kentelt
 * 
 */
public final class InputValidator {
	private String input;
	/**
	 * Visa credit card type
	 * 
	 * @see InputValidator#isValidCreditCard(byte)
	 */
	public static final byte CARD_VISA = 0;
	/**
	 * Master credit card type
	 * 
	 * @see InputValidator#isValidCreditCard(byte)
	 */
	public static final byte CARD_MASTERCARD = 1;
	/**
	 * American Express credit card type
	 * 
	 * @see InputValidator#isValidCreditCard(byte)
	 */
	public static final byte CARD_AMEX = 2;
	/**
	 * Discover credit card type
	 * 
	 * @see InputValidator#isValidCreditCard(byte)
	 */
	public static final byte CARD_DISCOVER = 3;

	/**
	 * Create a new Validator instance
	 * 
	 * @param input
	 *            Text to validate
	 */
	public InputValidator(String input) {
		this.input = input;
	}

	/**
	 * Checks if the given input is a valid email address
	 * <p>
	 * This routine performs the following checks
	 * <ul>
	 * <li>Presence and locations of the '@' and '.' characters. (RFC 2822)</li>
	 * <li>The total length of the email address is less than 320.</li>
	 * <li>The total length of the local-part is less than 64.</li>
	 * <li>The total length of the domain part is less than 255.</li>
	 * <li>if local/domain has invalid characters.</li>
	 * <li>for consecutive '.' characters(RFC 2822)</li>
	 * <li>for invalid characters like ()[]\;:,<> in the local part</li>
	 * <li>for proper formation of quoted strings as they are permitted</li>
	 * </ul>
	 * 
	 * This routine does not check for
	 * <ul>
	 * <li>Spaces, as a few email systems allow them.</li>
	 * </ul>
	 * </p>
	 * 
	 * @return true if the given input is a valid email address
	 */
	public final boolean isEmail() {
		if (input.indexOf('@') == -1 || input.indexOf('.') == -1)
			/* if the text does not contain @ or . */
			return false;
		if (input.length() > 320)
			/*
			 * if the length of the email address is greater than 320
			 * [64(Local-part)+255(Domain name)]
			 */
			return false;

		final int atIndx = input.indexOf('@');
		final int dotIndx = input.indexOf('.');
		final String localPart = input.substring(0, atIndx);
		final String domain = input.substring(atIndx + 1);

		System.out.println("Local part :" + localPart);
		System.out.println("Domain part :" + domain);

		/* Check if @ or . is at the wrong place */
		if (atIndx == 0 || atIndx == input.length() - 1 || dotIndx == 0 || dotIndx == input.length() - 1) {
			System.out.println(" @ or . Character at wrong place");
			return false;
		}
		/*
		 * Check if the local part is less than 64 and the domain part is less
		 * than 255
		 */
		if (localPart.length() > 64) {
			return false;
		}
		/* Check if the local part has 2 or more dots */
		char[] invalids = localPart.toCharArray();
		int numberOfQuotes = 0;
		for (int i = 0; i < invalids.length; i++) {
			// if (invalids[i] == '.')
			// numberOfDots++;
			if (invalids[i] == '"') {
				numberOfQuotes++;
			}
		}
		if (numberOfQuotes % 2 != 0)
			/* Has odd number of quotes */
			return false;
		if (hasInvalidChar(localPart, true))
			return false;
		if (StringUtil.isConsecutive(invalids, '.')) {
			System.out.println("has consecutive dots");
			return false;
		}
		if (localPart.indexOf(':') != -1)
			/* Emails cannot contain ':' but hostnames can */
			return false;
		return isValidHostName(domain);
	}

	/**
	 * 
	 * @param name
	 * @param isEmail
	 *            emails can contain spaces
	 * @return
	 */
	private boolean hasInvalidChar(String name, boolean isEmail) {
		final char[] invalidChars = { ' ', '@', '(', ')', '[', ']', '\\', ';', '<', '>' };
		/* Check for invalid characters in the entire email address */
		for (int i = isEmail ? 1 : 0; i < invalidChars.length; i++) {
			if (name.indexOf(invalidChars[i]) != -1) {
				System.out.println("Invalid charcter found");
				return true;
			}
		}
		return false;
	}

	private boolean isValidHostName(String host) {
		if (host.length() > 256) {
			return false;
		}
		int dotIndex = host.indexOf('.');
		if (dotIndex == -1 || dotIndex == 0 || dotIndex == host.length() - 1)
			/* Invalid dot location or no dot present */
			return false;
		int colonIdx = host.indexOf(':');
		if (colonIdx == 0 || colonIdx == host.length() - 1)
			/* Check if ':' used to specify ports is at the wrong place */
			return false;
		/* Check for invalid characters */
		if (hasInvalidChar(host, false))
			return false;
		if (StringUtil.isConsecutive(host, '.'))
			return false;
		return true;
	}

	/**
	 * Checks if the given input is a valid credit card number.
	 * <p>
	 * It performs a <a
	 * href="http://en.wikipedia.org/wiki/Luhn_algorithm">Luhn</a> validation
	 * after generic validation (MOD 10 varient)
	 * </p>
	 * 
	 * 
	 * @param type
	 *            Credit card type
	 * @return true if the credit card number set as input text is valid
	 */
	public final boolean isCreditCardNumber(byte type) {
		if (input.indexOf(' ') != -1 || StringUtil.containsAlphabets(input)) {
			System.out.println("Card contains alphabets or spaces");
			return false;
		}
		int start = 0;
		if (input.length() > 2) {
			start = Integer.parseInt(input.substring(0, 2));
		}
		switch (type) {
		case CARD_VISA:
			/* Visa cards start with number 4 and the length is either 13 or 16 */
			if (input.charAt(0) == '4') {
				if (input.length() != 13 || input.length() != 16) {
					return false;
				}
			}
			break;
		case CARD_MASTERCARD:
			/* Master cards start with 51 - 55 and has a length of 16 digits */
			if (!(start >= 51 && start <= 55) || input.length() != 16) {
				System.out.println("Does not start with 51-55");
				return false;
			}
			break;
		case CARD_AMEX:
			/* American Express cards start with 34 or 37 and is 15 digits long */
			if (start != 34 && start != 37 || input.length() != 15) {
				System.out.println("Simple validation failed ");
				return false;
			}
			break;
		case CARD_DISCOVER:
			/* Discover cards start with 6011 */
			if (input.length() > 5) {
				start = Integer.parseInt(input.substring(0, 4));
				System.out.println("start " + start);
				if (start != 6011 || input.length() != 16)
					System.out.println("Simple validation failed ");
				return false;
			}
			break;
		}
		/* if standard validations are over perform luhn validation */
		return CheckSum.doLuhnMod10(input);
	}

	public final boolean isURL() {
		// if (input.startsWith("http://")) {
		return isValidHostName(input);
		// }
		// return true;
	}

	/**
	 * Set the input
	 * 
	 * @param input
	 *            to be validated
	 * @throws NullPointerException
	 *             if <code>input</code> is null
	 */
	public final void setInput(String input) {
		if (input == null)
			throw new NullPointerException();
		this.input = input;
	}
}
